/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.classes;

import org.griphyn.vdl.classes.*;
import java.util.*;
import java.io.IOException;
import java.io.Writer;
import java.io.Serializable;

/**
 * This class defines the formal arguments to a <code>Transformation</code>.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 50 $
 *
 * @see Transformation
 */
public class Declare extends VDL implements Serializable
{
  /**
   * Each formal argument has a name to which it is bound. 
   */
  private String m_name;

  /**
   * Each formal argument has a specific type. There are currently
   * only the type of {@link Scalar} and {@link List}.
   */
  private int m_containerType = Value.SCALAR;

  /**
   * For linking the DAG we need to know if the argument is passed into
   * the transformation, produced by the transformation, or has some 
   * other behavior.
   */
  private int m_link = LFN.NONE;

  /**
   * The default value of a formal argument is optional. The notion of a
   * default value is taken from C++.
   */
  private Value m_value;

  /**
   * Default ctor: needed for JDO
   */
  public Declare()
  {
    super();
  }

  /**
   * ctor: Construct a new formal argument with a binding and default 
   * container type.
   *
   * @param name is the binding.
   * @param ct is the container type, the type of the argument.
   * @throws IllegalArgumentException if the container type is outside
   * the legal range [{@link Value#SCALAR}, {@link Value#LIST}].
   */
  public Declare( String name, int ct )
    throws IllegalArgumentException
  {
    super();
    this.m_name = name;
    if ( Value.isInRange(ct) ) 
      this.m_containerType = ct;
    else
      throw new IllegalArgumentException( "container type outside legal range" );
  }

  /**
   * ctor: Construct a new formal argument with a binding and default 
   * container type, as well as a linkage for the argument.
   *
   * @param name is the binding.
   * @param ct is the container type, the type of the argument.
   * @param link is the linkage type for the argument.
   * @throws IllegalArgumentException if the container type is outside
   * the legal range [{@link Value#SCALAR}, {@link Value#LIST}], or
   * the linkage is outside [{@link LFN#NONE}, {@link LFN#INOUT}].
   */
  public Declare( String name, int ct, int link )
    throws IllegalArgumentException
  {
    super();
    this.m_name = name;
    if ( Value.isInRange(ct) ) 
      this.m_containerType = ct;
    else
      throw new IllegalArgumentException( "container type outside legal range" );

    if ( LFN.isInRange(link) ) 
      this.m_link = link;
    else
      throw new IllegalArgumentException( "linkage type outside legal range" );
  }

  /**
   * ctor: Construct a new formal argument with a binding and default 
   * value. The container type will be determined from the default value.
   *
   * @param name is the binding.
   * @param value is either a {@link Scalar} or {@link List} value.
   */
  public Declare( String name, Value value )
  {
    super();
    this.m_name = name;
    this.m_value = value;
    this.m_containerType = value.getContainerType();
  }


  /**
   * ctor: Construct a new formal argument with a binding and default 
   * value. The container type will be determined from the default value.
   * The linkage is set separately.
   *
   * @param name is the binding.
   * @param value is either a {@link Scalar} or {@link List} value.
   * @param link is the linkage of the value for DAG creation.
   * @throws IllegalArgumentException if the linkage is outside
   * [{@link LFN#NONE}, {@link LFN#INOUT}].
   */
  public Declare( String name, Value value, int link )
    throws IllegalArgumentException
  {
    super();
    this.m_name = name;
    this.m_value = value;
    this.m_containerType = value.getContainerType();
    if ( LFN.isInRange(link) ) 
      this.m_link = link;
    else
      throw new IllegalArgumentException( "linkage type outside legal range" );
  }

  /**
   * Accessor: Obtains the optional default value for the parameter.
   *
   * @return the default as {@link Scalar} or {@link List}, or null,
   * if not default value was registered.
   * @see #setValue(Value)
   */
  public Value getValue()
  {
    return this.m_value; 
  }

  /**
   * Accessor: Obtains the bound name of the formal argument.
   *
   * @return the name by which an actual arguments in a {@link Derivation}
   * can refer to this formal argument.
   * @see #setName( java.lang.String )
   */
  public String getName()
  {
    return this.m_name; 
  }

  /**
   * Accessor: Obtains the linkage type of the formal argument.
   *
   * @return the linkage as an integer.
   * @see #setLink(int)
   * @see LFN#NONE
   * @see LFN#INPUT
   * @see LFN#OUTPUT
   * @see LFN#INOUT
   */
  public int getLink()
  {
    return this.m_link; 
  }

  /**
   * Accessor: Obtains the container type. Note that the registered
   * container type will be taken in case there is no default value.
   * Otherwise the container type of the default value will be taken.
   *
   * @return the container type of the formal argument.
   * @see #setContainerType(int)
   * @see Value#SCALAR
   * @see Value#LIST
   */
  public int getContainerType()
  {
    return ( m_value != null ) ? 
      m_value.getContainerType() : 
      m_containerType;
  }

  /**
   * Accessor: Establishes a new name with this formal argument to bind
   * to by an actual argument. FIXME: Note that no checks will be done
   * concerning the uniqueness of the new name.
   *
   * @param name is the new binding.
   * @see #getName()
   */
  public void setName( String name )
  {
    this.m_name = name; 
  }

  /**
   * Accessor: Sets a new linkage type for the formal argument. 
   *
   * @param link is the new linkage type from {@link LFN}.
   * @throws IllegalArgumentException, if the argument is outside
   * the valid range.
   * @see #getLink()
   */
  public void setLink( int link )
    throws IllegalArgumentException
  {
    if ( ! LFN.isInRange(link) )
      throw new IllegalArgumentException();
    this.m_link = link;
  }

  /**
   * Accessor: Sets a new container type for the formal argument. If
   * a default value is known, the new container type must match the
   * default value's container type.
   *
   * @param containerType is the new integer describing a container type.
   * @throws IllegalArgumentException if the container type is neither
   * {@link Value#SCALAR} nor {@link Value#LIST}.
   * @see #getContainerType()
   */
  public void setContainerType( int containerType )
    throws IllegalArgumentException
  {
    if ( m_value == null ) {
      // no default value known, need to set container type
      if ( Value.isInRange(containerType) )
	this.m_containerType = containerType;
      else
	throw new IllegalArgumentException( "container type outside legal range");
    } else {
      // there is a default value, new type must match default
      if ( m_value.getContainerType() != containerType ) 
	throw new IllegalArgumentException( "new container type does not match container type of default value" );
    }
  }

  /**
   * Accessor: Sets or overwrites the optional default value of a formal
   * argument. FIXME: A value of null should be usable to kill a default
   * value. The new default must match the container type.
   *
   * @param value is the new default value. 
   * @throws IllegalArgumentException if the container type of the new
   * value and of the registered container type for the parameter don't
   * match.
   * @see #getValue()
   */
  public void setValue(Value value)
    throws IllegalArgumentException
  { 
    if ( value.getContainerType() == this.m_containerType )
      this.m_value = value;
    else
      // container types do not match
      throw new IllegalArgumentException( "container type of new value does not match declared container type" );
  }

  /**
   * Dumps the content of the use element into a string for human consumption.
   *
   * @return a textual description of the element and its attributes.
   * Be advised that these strings might become large.
   */
  public String toString()
  {
    String value = this.m_value == null ? "" : this.m_value.toString();
    StringBuffer result =
      new StringBuffer( 12 + this.m_name.length() + value.length() );

    result.append( LFN.toString( this.m_link ) );
    result.append( ' ' );
    result.append( this.m_name );
    if ( this.m_containerType == Value.LIST )
      result.append("[]");

    if ( this.m_value != null ) {
      result.append('=');
      result.append(value);
    }
    return result.toString();
  }

  /**
   * Dumps the content of the declaration into a string for human
   * consumption.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @exception IOException if something fishy happens to the stream.
   */
  public void toString( Writer stream )
    throws IOException
  {
    stream.write( LFN.toString( this.m_link ) );
    stream.write( ' ' );
    stream.write( escape(this.m_name) );
    if ( this.m_containerType == Value.LIST )
      stream.write("[]");

    if ( this.m_value != null ) {
      stream.write('=');
      this.m_value.toString(stream);
    }
  }

  /**
   * Dump the state of the current element as XML output. This function
   * traverses all sibling classes as necessary, and converts the data
   * into pretty-printed XML output. The stream interface should be able
   * to handle large output efficiently, if you use a buffered writer.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @param indent is a <code>String</code> of spaces used for pretty
   * printing. The initial amount of spaces should be an empty string.
   * The parameter is used internally for the recursive traversal.
   * If a <code>null</code> value is specified, no indentation nor
   * linefeeds will be generated. 
   * @param namespace is the XML schema namespace prefix. If neither
   * empty nor null, each element will be prefixed with this prefix,
   * and the root element will map the XML namespace. 
   * @exception IOException if something fishy happens to the stream.
   */
  public void toXML( Writer stream, String indent, String namespace )
    throws IOException
  {
    String newline = System.getProperty( "line.separator", "\r\n" );
    String tag = ( namespace != null && namespace.length() > 0 ) ?
      namespace + ":declare" : "declare";

    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( '<' );
    stream.write( tag );

    writeAttribute( stream, " name=\"", this.m_name ); // null-safe
    if ( LFN.isInRange(this.m_link) )
      writeAttribute( stream, " link=\"", LFN.toString(this.m_link) );

    if ( this.m_containerType == Value.LIST ) {
      stream.write( " container=\"list\"" );
    } else if ( this.m_containerType == Value.SCALAR ) {
      stream.write( " container=\"scalar\"" );
    }

    if ( this.m_value == null ) {
      // no default value
      stream.write( "/>" );
    } else {
      // there is a default value
      String newindent = indent==null ? null : indent + "  ";
      stream.write( '>' );
      if ( indent != null ) stream.write( newline );

      // dump content
      this.m_value.toXML( stream, newindent, namespace );

      // write close tag
      if ( indent != null && indent.length() > 0 ) stream.write( indent );
      stream.write( "</" );
      stream.write( tag );
      stream.write( '>' );
    }
    if ( indent != null ) stream.write( newline );
  }
}
